﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SystemViewModel.cs">
//   J. Prinz
// </copyright>
// <summary>
//   ViewModel of all SystemUnitClasses in an AutomationML Document, which together represent a ClassModel of an exporting or an importing system
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace AutomationMLMapper.ViewModels.SystemViewModels
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Linq;
    using System.Windows.Data;
    using System.Windows.Input;

    using AMLEngineExtensions;
    using CAEX_ClassModel;

    using AutomationMLMapper.AutomationML.Extensions;
    using AutomationMLMapper.ViewModels.CAEXObjectCollectionViewModels;
    using AutomationMLMapper.ViewModels.CAEXObjectViewModels;

    using Microsoft.Win32;
    using AutomationMLMapper.MVVM_Framework;

    /// <summary>
    ///   ViewModel of all SystemUnitClasses in an AutomationML Document, which together represent a ClassModel of an exporting or an importing system
    /// </summary>
    public class SystemViewModel : CAEXObjectViewModel<SystemUnitClassLibType>
    {
        #region Fields

        /// <summary>
        ///   <see cref="AmlDocumentRepresentation" />
        /// </summary>
        private CAEXFileTypeViewModel amlDocumentRepresentation;

        /// <summary>
        ///   <see cref="ImportRoleLibsCommand" />
        /// </summary>
        private RelayCommand<object> importRoleLibsCommand;

        /// <summary>
        ///   <see cref="LoadSystemCommand" />
        /// </summary>
        private RelayCommand<object> loadSystemCommand;
        
        /// <summary>
        ///   <see cref="SystemType" />
        /// </summary>
        private string systemType = string.Empty;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///   Initializes a new instance of the <see cref="SystemViewModel" /> class.
        /// </summary>
        public SystemViewModel()
        {
            this.ClassesCollection = new SystemUnitClassCollectionViewModel(this);
            this.RolesCollection = new RoleClassCollectionViewModel(this);
            this.RoleLibsCollection = new CAEXObjectCollectionViewModel<RoleClassLibViewModel>(this);

            this.RolesCollection.GroupDescriptions.Add(new PropertyGroupDescription("ContainerName")); 
            this.ClassesCollection.GroupDescriptions.Add(new PropertyGroupDescription("ContainerName"));
            this.ClassesCollection.GroupDescriptions.Add(new PropertyGroupDescription("LibraryName"));
            this.AmlDocumentRepresentation = new CAEXFileTypeViewModel { SystemViewModel = this };
            this.EngineMessages = new AutomationMLMessage();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Name Changes are propagated to the managed Collection because the Name is used in a Group Definition
        /// </summary>
        public override string Name
        {
            get
            {
                return base.Name;
            }

            set
            {
                base.Name = value;
                this.RolesCollection.View.Refresh();
                this.ClassesCollection.View.Refresh();
            }
        }

        /// <summary>
        ///   The ViewModel of the AmlDocument
        /// </summary>
        public CAEXFileTypeViewModel AmlDocumentRepresentation
        {
            get
            {
                return this.amlDocumentRepresentation;
            }

            set
            {
                this.amlDocumentRepresentation = value;
                this.OnNotifyPropertyChanged("AmlDocumentRepresentation");
            }
        }

        /// <summary>
        ///   Collection of Classes of the System
        /// </summary>
        public SystemUnitClassCollectionViewModel ClassesCollection { get; set; }

        /// <summary>
        ///   Messages, created from the AutomationML Engine
        /// </summary>
        public AutomationMLMessage EngineMessages { get; set; }

        /// <summary>
        ///   Returns true, if any SystemUnitClass is not valid
        /// </summary>
        public bool HasErrors
        {
            get
            {
                return !this.IsValid || this.ClassesCollection.Elements.Any(p => !p.IsValid);
            }
        }

        /// <summary>
        ///   Import the RoleClassLibraries from an AutomationML Document File which are not already part of this CaexDocument
        /// </summary>
        public ICommand ImportRoleLibsCommand
        {
            get
            {
                return this.importRoleLibsCommand
                       ??
                       (this.importRoleLibsCommand =
                        new RelayCommand<object>(this.ImportRoleLibsCommandExecute, ImportRoleLibsCommandCanExecute));
            }
        }

        /// <summary>
        ///   Load the System Definition AutomationML File
        /// </summary>
        public ICommand LoadSystemCommand
        {
            get
            {
                return this.loadSystemCommand
                       ??
                       (this.loadSystemCommand =
                        new RelayCommand<object>(this.LoadSystemCommandExecute, LoadSystemCommandCanExecute));
            }
        }

        /// <summary>
        ///   The ViewModel of the MainView of the Application
        /// </summary>
        public AutomationMLMapperViewModel MainViewModel { get; set; }

        /// <summary>
        ///   the MappingClassLibrary, created for ClassToClass-Mappings
        /// </summary>
        public SystemUnitClassLibType MappingClassLibrary { get; set; }
        
        /// <summary>
        ///   Collection of RoleClass-Libraries of the System
        /// </summary>
        public CAEXObjectCollectionViewModel<RoleClassLibViewModel> RoleLibsCollection { get; set; }

        /// <summary>
        ///   Collection of RoleClasses of the System
        /// </summary>
        public RoleClassCollectionViewModel RolesCollection { get; set; }

        /// <summary>
        ///   Name of the Simulated System Type (Export or Import)
        /// </summary>
        public string SystemType
        {
            get
            {
                return this.systemType;
            }

            set
            {
                this.systemType = value;
                this.OnNotifyPropertyChanged("SystemType");
            }
        }

        /// <summary>
        ///   The SystemUnitClassMapping-Object from the SystemUnitClass-MappingLibrary
        /// </summary>
        public SystemUnitFamilyType SystemUnitClassMappingObject { get; set; }

        /// <summary>
        ///   The SystemUnitClassMapping-Role from the Mapping-RoleClassLibrary
        /// </summary>
        public RoleFamilyType SystemUnitClassMappingRole { get; set; }

        #endregion

        #region Public Indexers

        /// <summary>
        /// Validation method
        /// </summary>
        /// <param name="propertyName">
        /// Name of the validated property
        /// </param>
        /// <returns>
        /// String as error notification
        /// </returns>
        public override string this[string propertyName]
        {
            get
            {
                // base class performs libraryName validation test for empty string
                var result = base[propertyName];
                if (result != null)
                {
                    return result;
                }

                if (!this.IsValid)
                {
                    switch (propertyName)
                    {
                        case "Name":
                            if (this.ContainsClassLibWithName(this.Name))
                            {
                                return "Please enter a unique Systemname";
                            }

                            this.IsValid = true;
                            break;
                    }
                }
                return null;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        ///  Add SystemUnitClass <see cref="SystemUnitClassType"/>and its ViewModel <see cref="SystemUnitClassViewModel"/> 
        ///  to the ClassesCollection.
        /// </summary>
        /// <param name="caexObjectType">
        /// The caex Object Type.
        /// </param>
        /// <returns>
        /// The interface to the added CAEXObject. 
        /// </returns>
        public override ICAEXObject AddElement(Type caexObjectType)
        {
            if (caexObjectType == typeof(SystemUnitClassViewModel))
            {
                var systemUnitClassRepresentation =
                    new SystemUnitClassViewModel(this.CaexObject.SystemUnitClass.Append())
                        {
                           IsValid = false, 
                           ContainerViewModel = this,
                           LibraryCaexObject = this.CaexObject,
                           TheSimulatedSystem = this
                        };

                base.AddElement(caexObjectType);

                return systemUnitClassRepresentation;
            }

            return null;
        }

        /// <summary>
        /// Load a system, represented in a File with provided filepath.
        /// </summary>
        /// <param name="filepath">
        /// The filepath.
        /// </param>
        public void LoadSystemWithPath(string filepath)
        {
            this.Name = Path.GetFileNameWithoutExtension(filepath);
            this.ReadAutomationML(filepath, false);
        }

        /// <summary>
        ///   revalidation of all SystemUnitClass in the Collection <see cref="ClassesCollection" />
        /// </summary>
        public override void ReValidate()
        {
            this.ClassesCollection.Elements.ForEach(p => p.ReValidate());
        }

        #endregion

        #region Methods

        /// <summary>
        /// A Name of any classlib should be unique in the Caex Document
        /// </summary>
        /// <param name="systemName">
        /// The system Name.
        /// </param>
        /// <returns>
        /// True, if Name is not unique 
        /// </returns>
        internal bool ContainsClassLibWithName(string systemName)
        {
            return this.AmlDocumentRepresentation.CaexObject != null
                   &&
                   this.AmlDocumentRepresentation.CaexObject.SystemUnitClassLib.All().Count(
                       p => p.Name.Exists() && p.Name.Value.Equals(systemName, StringComparison.OrdinalIgnoreCase)) > 1
                   &&
                   this.AmlDocumentRepresentation.CaexObject.RoleClassLib.All().Count(
                       p => p.Name.Exists() && p.Name.Value.Equals(systemName, StringComparison.OrdinalIgnoreCase)) > 1
                   &&
                   this.AmlDocumentRepresentation.CaexObject.InterfaceClassLib.All().Count(
                       p => p.Name.Exists() && p.Name.Value.Equals(systemName, StringComparison.OrdinalIgnoreCase)) > 1;

            // &&
            // this.AmlDocumentRepresentation.CaexObject.InstanceHierarchy.All().Count(
            // p => p.Name.Exists() && p.Name.Value.Equals(systemName, StringComparison.OrdinalIgnoreCase)) > 1;
        }

        /// <summary>
        /// A Name of a class should be unique in the collection
        /// </summary>
        /// <param name="systemUnitclass">
        ///  SystemUnitClass which should have a unique name
        /// </param>
        /// <returns>
        /// False, if Name is not unique 
        /// </returns>
        internal bool IsUnique(SystemUnitClassViewModel systemUnitclass)
        {
            foreach (var sc in this.ClassesCollection.Elements.Where(
                p => p.CaexObject.GetParentNodeName().Equals(systemUnitclass.CaexObject.GetParentNodeName())))
            {
                if (!sc.Equals(systemUnitclass) && sc.FullPath == systemUnitclass.FullPath)
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Create an empty library.
        /// </summary>
        /// <param name="libraryName">
        /// The library Name.
        /// </param>
        internal void CreateEmptyLibrary(string libraryName)
        {
            this.CaexObject = this.AmlDocumentRepresentation.CaexObject.SystemUnitClassLib.Append();
            this.Name = libraryName;
            
            // create an initial empty SystemUnitClass Lib 
            this.LoadCaexToRepresentationModel();
        }

        /// <summary>
        /// Load and merge the CaexDocument from input filename
        /// </summary>
        /// <param name="filePath">
        /// The file Path.
        /// </param>
        /// <returns>
        /// the loaded CAEXDocument 
        /// </returns>
        internal CAEXDocument LoadAndMerge(string filePath)
        {
            var messages = new List<string>();

            var document = CAEXDocument.LoadFromFile(filePath);
            if (document != null)
            {
                document.Merge(ref messages);
                this.EngineMessages.Messages = new ObservableCollection<string>(messages);
            }

            return document;
        }

        /// <summary>
        ///   Reload an already loaded AutomationML Document to the existing representation object
        /// </summary>
        internal void ReLoad()
        {
            this.ReadAutomationML(this.AmlDocumentRepresentation.FilePath, true);
        }

        /// <summary>
        /// Test, if the <see cref="ImportRoleLibsCommand"/> can execute.
        /// </summary>
        /// <param name="parameter">
        /// The parameter (unused).
        /// </param>
        /// <returns>
        /// always true 
        /// </returns>
        private static bool ImportRoleLibsCommandCanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        /// Test, if the <see cref="LoadSystemCommand"/> can execute.
        /// </summary>
        /// <param name="parameter">
        /// The parameter (unused).
        /// </param>
        /// <returns>
        /// The System.Boolean. 
        /// </returns>
        private static bool LoadSystemCommandCanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        ///  All RoleClasses from the provided roleClassLib are added to the Collection of RoleClasses in the
        ///  UI-Representation
        /// </summary>
        /// <param name="roleClassLib">
        /// The role Class Lib.
        /// </param>
        private void AddRoleClassesFromRoleClassLib(RoleClassLibType roleClassLib)
        {
            var libRepresentation = new RoleClassLibViewModel(roleClassLib) { Owner = this };
            libRepresentation.LoadCaexToRepresentationModel();
            this.RoleLibsCollection.Elements.Add(libRepresentation);

            var roleClassesOfLibrary = new List<CAEXObject>();
            roleClassLib.GetAllLibraryClasses(roleClassesOfLibrary);

            foreach (var roleRepresentation in
                roleClassesOfLibrary.Select(
                    roleClass =>
                    new RoleClassViewModel((RoleClassType)roleClass)
                        {
                            ContainerViewModel = libRepresentation, LibraryCaexObject = roleClassLib 
                        }))
            {
                roleRepresentation.LoadCaexToRepresentationModel();
                this.RolesCollection.Elements.Add(roleRepresentation);
            }
        }

        /// <summary>
        /// The <see cref="ImportRoleLibsCommand"/> Execute Action
        /// </summary>
        /// <param name="parameter">
        /// The parameter.
        /// </param>
        private void ImportRoleLibsCommandExecute(object parameter)
        {
            var ofd = new OpenFileDialog
                {
                    Filter = "AutomationML  (*.aml)|*.aml", 
                    Title = "RoleClass Libraries - AutomationML File", 
                    DefaultExt = "aml"
                };
            var result = ofd.ShowDialog();
            if (result.Value)
            {
                var documentWithRoleClassLibs = this.LoadAndMerge(ofd.FileName);

                foreach (RoleClassLibType roleClassLib in documentWithRoleClassLibs.CAEXFile.RoleClassLib)
                {
                    if (this.RoleLibsCollection.Elements.All(p => p.Name != roleClassLib.Name.Value))
                    {
                        var importedRoleClassLib =
                            this.AmlDocumentRepresentation.CaexObject.Import_RoleClassLibHierarchy(roleClassLib);
                        if (importedRoleClassLib != null)
                        {
                            this.AddRoleClassesFromRoleClassLib(importedRoleClassLib);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Load a CaexDocument from input filename and create an <see cref="AmlDocumentRepresentation"/>
        /// </summary>
        /// <param name="filePath">
        /// The file Path of input file 
        /// </param>
        /// <param name="reuse">
        /// reuse the current AMLDocumentRepresentation Object (this is a reload) 
        /// </param>
        private void LoadAMLDocumentRepresentation(string filePath, bool reuse)
        {
            var document = this.LoadAndMerge(filePath);
            if (document != null)
            {
                if (!reuse || this.AmlDocumentRepresentation == null)
                {
                    if (this.AmlDocumentRepresentation != null
                        && this.MainViewModel.OpenAutomationMLFiles.Elements.Contains(this.AmlDocumentRepresentation))
                    {
                        this.MainViewModel.OpenAutomationMLFiles.Elements.Remove(this.AmlDocumentRepresentation);
                    }

                    this.AmlDocumentRepresentation = new CAEXFileTypeViewModel(document.CAEXFile)
                        {
                           CaexDocument = document, FilePath = filePath, SystemViewModel = this 
                        };

                    this.MainViewModel.OpenAutomationMLFiles.Elements.Add(this.AmlDocumentRepresentation);
                }
                else
                {
                    this.AmlDocumentRepresentation.CaexDocument = document;
                }
            }
        }

        /// <summary>
        /// The <see cref="LoadSystemCommand"/> Execution Action
        /// </summary>
        /// <param name="parameter">
        /// The parameter (unused). 
        /// </param>
        private void LoadSystemCommandExecute(object parameter)
        {
            var ofd = new OpenFileDialog
                {
                    Filter = "AutomationML  (*.aml)|*.aml", 
                    Title = "System Definition - AutomationML File", 
                    DefaultExt = "aml"
                };
            var result = ofd.ShowDialog();
            if (result.Value)
            {
                this.LoadSystemWithPath(ofd.FileName);
            }
        }

        /// <summary>
        /// Read the AutomationML File and extract the Classes from the SystemUnitClass-Library, the Name of the Simulated System and the Name
        /// of the SystemUnitClass-Library in the AutomationML File should match
        /// </summary>
        /// <param name="filename">
        /// The filename.
        /// </param>
        /// <param name="reuse">
        /// The reuse.
        /// </param>
        private void ReadAutomationML(string filename, bool reuse)
        {
            if (File.Exists(filename))
            {
                this.RolesCollection.Elements.Clear();
                this.RoleLibsCollection.Elements.Clear();
                this.ClassesCollection.Elements.Clear();

                this.LoadAMLDocumentRepresentation(filename, reuse);
                foreach (RoleClassLibType lib in this.AmlDocumentRepresentation.CaexObject.RoleClassLib)
                {
                    this.AddRoleClassesFromRoleClassLib(lib);
                }

                foreach (SystemUnitClassLibType lib in this.AmlDocumentRepresentation.CaexObject.SystemUnitClassLib)
                {
                    if (lib.Name.Value == this.Name)
                    {
                        this.CaexObject = lib;
                        this.LoadCaexToRepresentationModel();
                    }

                    this.CreateClassTree(lib, lib.SystemUnitClass);
                }

                this.ReValidate();
            }
        }

        /// <summary />
        /// <param name="lib"></param>
        /// <param name="systemUnitClasses"></param>
        private void CreateClassTree ( SystemUnitClassLibType lib,  IEnumerable systemUnitClasses )
        {
            foreach (SystemUnitFamilyType suclass in systemUnitClasses)
            {
                var systemUnitClassRepresentation = new SystemUnitClassViewModel(suclass)
                {
                    IsValid = true,
                    ContainerViewModel = this,
                    LibraryCaexObject = lib,
                    TheSimulatedSystem = this
                };
                systemUnitClassRepresentation.LoadCaexToRepresentationModel();
                this.ClassesCollection.Elements.Add(systemUnitClassRepresentation);

                this.CreateClassTree(lib, suclass.SystemUnitClass);
            }
        }

        #endregion
    }
}